﻿namespace Blaze.Security
{
    using System;

    /// <summary>
    /// 用于内存加密<see cref="int"/>型的变量。
    /// </summary>
    public struct SecureInt : IEquatable<SecureInt>
    {
        /// <summary>
        /// 当检测到内存作弊时调用此方法（只会触发一次，之后会被设为null）
        /// </summary>
        public static Action onCheatingDetected;

        private static int cryptoKey = 424849;
        private int currentCryptoKey;
        private int hiddenValue;
        public int fakeValue;
        private bool isInited;

        private SecureInt(int value)
        {
            currentCryptoKey = cryptoKey;
            hiddenValue = value;
            fakeValue = 0;
            isInited = true;
        }

        /// <summary>
        /// 设置新的加密密钥。
        /// </summary>
        /// <param name="newKey">新的密钥</param>
        public static void SetNewCryptoKey(int newKey)
        {
            cryptoKey = newKey;
        }

        /// <summary>
        /// 获取加密后的值。
        /// </summary>
        public int GetEncrypted()
        {
            if (currentCryptoKey != cryptoKey)
            {
                hiddenValue = InternalDecrypt();
                hiddenValue = Encrypt(hiddenValue, cryptoKey);
                currentCryptoKey = cryptoKey;
            }
            return hiddenValue;
        }

        /// <summary>
        /// 将变量设置为加密后的值。
        /// </summary>
        /// <param name="encrypted">加密后的值</param>
        public void SetEncrypted(int encrypted)
        {
            hiddenValue = encrypted;
            if (onCheatingDetected != null)
            {
                fakeValue = InternalDecrypt();
            }
        }

        /// <summary>
        /// 使用默认的密钥加密变量。
        /// </summary>
        /// <param name="value">明文</param>
        /// <returns>密文</returns>
        public static int Encrypt(int value)
        {
            return Encrypt(value, 0);
        }

        /// <summary>
        /// 使用指定的密钥加密变量。
        /// </summary>
        /// <param name="value">明文</param>
        /// <param name="key">密钥</param>
        /// <returns>密文</returns>
        public static int Encrypt(int value, int key)
        {
            if (key == 0)
            {
                return value ^ cryptoKey;
            }
            return value ^ key;
        }

        /// <summary>
        /// 使用默认的密钥解密变量。
        /// </summary>
        /// <param name="value">密文</param>
        /// <returns>明文</returns>
        public static int Decrypt(int value)
        {
            return Decrypt(value, 0);
        }

        /// <summary>
        /// 使用指定的密钥解密变量。
        /// </summary>
        /// <param name="value">密文</param>
        /// <param name="key">密钥</param>
        /// <returns>明文</returns>
        public static int Decrypt(int value, int key)
        {
            if (key == 0)
            {
                return value ^ cryptoKey;
            }
            return value ^ key;
        }

        private int InternalDecrypt()
        {
            if (!isInited)
            {
                currentCryptoKey = cryptoKey;
                hiddenValue = Encrypt(0);
                fakeValue = 0;
                isInited = true;
            }

            var key = cryptoKey;

            if (currentCryptoKey != cryptoKey)
            {
                key = currentCryptoKey;
            }

            var decrypted = Decrypt(hiddenValue, key);

            if (onCheatingDetected != null && fakeValue != 0 && decrypted != fakeValue)
            {
                onCheatingDetected();
                onCheatingDetected = null;
            }

            return decrypted;
        }

        public static implicit operator SecureInt(int value)
        {
            var secure = new SecureInt(Encrypt(value));
            if (onCheatingDetected != null)
            {
                secure.fakeValue = value;
            }
            return secure;
        }

        public static implicit operator int(SecureInt value)
        {
            return value.InternalDecrypt();
        }

        public static SecureInt operator ++(SecureInt input)
        {
            var decrypted = input.InternalDecrypt() + 1;
            input.hiddenValue = Encrypt(decrypted, input.currentCryptoKey);

            if (onCheatingDetected != null)
            {
                input.fakeValue = decrypted;
            }
            return input;
        }

        public static SecureInt operator --(SecureInt input)
        {
            var decrypted = input.InternalDecrypt() - 1;
            input.hiddenValue = Encrypt(decrypted, input.currentCryptoKey);

            if (onCheatingDetected != null)
            {
                input.fakeValue = decrypted;
            }
            return input;
        }

        public override bool Equals(object obj)
        {
            if (!(obj is SecureInt))
                return false;

            var oi = (SecureInt) obj;
            return (hiddenValue == oi.hiddenValue);
        }

        public bool Equals(SecureInt obj)
        {
            return hiddenValue == obj.hiddenValue;
        }

        public override int GetHashCode()
        {
            return InternalDecrypt().GetHashCode();
        }

        public override string ToString()
        {
            return InternalDecrypt().ToString();
        }

        public string ToString(string format)
        {
            return InternalDecrypt().ToString(format);
        }

        public string ToString(IFormatProvider provider)
        {
            return InternalDecrypt().ToString(provider);
        }

        public string ToString(string format, IFormatProvider provider)
        {
            return InternalDecrypt().ToString(format, provider);
        }
    }
}